#include "orbitvirtualmachine.h"
#include <fstream>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <sstream>
#include "constants.h"

OrbitVirtualMachine::OrbitVirtualMachine()
{
    init();
    m_logEnabled = false;
}

OrbitVirtualMachine::~OrbitVirtualMachine()
{
    /// ненужная строчка
}

void OrbitVirtualMachine::init()
{
    m_data.clear();
    m_code.clear();
    m_data.resize(16384);
    m_code.resize(16384);
    m_inReg.clear();
    m_outReg.clear();
    m_status = false;
    m_count = 0;
    m_index = 0;
}
void OrbitVirtualMachine::openFile(const std::string &fileName)
{
    init();
    m_logFile = fileName+".log";

    char buffer [12] = {'\0'};
    FILE * pFile;
    pFile = fopen (fileName.c_str() , "rb");
    if (pFile == NULL) perror ("Error opening file");
    else
    {
        while ( (!feof (pFile)))
        {
            fread (buffer,1,sizeof(buffer),pFile);
            parseCommand(buffer, m_index);
        }
        fclose (pFile);
    }
    statsOutput();

}

void OrbitVirtualMachine::parseCommand(const char *buff, int index)
{
    bool isEven = index%2 == 0;
    double data = 0.0;
    long int code = 0;
    if (isEven)
        {
            data = *(double*)(buff);
            code = *(long int*)(buff+8);
        }
    else
        {
            code = *(long int*)(buff);
            data = *(double*)(buff+4);
        }
    m_code[index] = code;
    m_data[index] = data;
    m_index++;
}

void OrbitVirtualMachine::setInputPort(int number, double val)
{
    int numberCorr = number & 0x3FFF;
    m_inReg.insert(number, val);
}

void OrbitVirtualMachine::setScenario(int number)
{
    setInputPort(0x3E80, number);
}

void OrbitVirtualMachine::nextStep()
{
    m_index = 0;
    long int code;
    int opCode;
    int imm;
    int r1;
    int r2;
    while (m_index < m_data.size())
    {
        code = m_code[m_index];
        if (code == 0)
        {
            m_index++;
            continue;
        }
        int opType = (code & 0xF0000000);
        if (opType == 0)
        {
            opCode = ((code & 0xFF000000) >> 24);
            imm = (code>>21) & 7;
            r1 = ((code & 0x3FFF));
            executeS(opCode, imm, r1);
        }
        else
        {
            opCode = ((code & 0xF0000000) >> 28);
            r1 = ((code & 0xFFFC000) >> 14);
            r2 = (code & 0x3FFF);
            executeD(opCode, r1, r2);
        }
        m_index++;
    }
    statsOutput();
    m_count++;
}

void OrbitVirtualMachine::statsOutput()
{
    if (!m_logEnabled)
        return;
    FILE * outFile;
    outFile = fopen (m_logFile.c_str() , (m_count == 0) ? "w" : "a");
    if (!outFile)
        return;
    fprintf(outFile, "%s", getStatusString().c_str());
    fclose(outFile);
}

void OrbitVirtualMachine::getPort(int number, double &val)
{
    double res = m_outReg.value(number);
    val = res;
}

 void OrbitVirtualMachine::dAdd(int r1, int r2)
{
    m_data[m_index] = m_data[r1] + m_data[r2];
}

 void OrbitVirtualMachine::dSub(int r1, int r2)
{
    m_data[m_index] = m_data[r1] - m_data[r2];
}

 void OrbitVirtualMachine::dMult(int r1, int r2)
{
    m_data[m_index] = m_data[r1] * m_data[r2];
}

 void OrbitVirtualMachine::dDiv(int r1, int r2)
{
    m_data[m_index] = (m_data[r2] == 0.0) ? 0.0 : (m_data[r1] /m_data[r2]);
}

 void OrbitVirtualMachine::dPhi(int r1, int r2)
{
    m_data[m_index] =  m_status ? m_data[r1] : m_data[r2];
}


 void OrbitVirtualMachine::dOutput(int r1, int r2)
{
   /// TODO
    double res = m_data[r2];
    m_outReg.insert(r1, res);
}

 void OrbitVirtualMachine::sNoop(int /*imm*/, int /*r1*/)
{

}

 void OrbitVirtualMachine::sCmpz( int imm, int r1)
{
    double data = m_data[r1];
    switch(imm)
    {
    case 0:
        m_status = (data < 0.0);
        break;
    case 1:
        m_status = (data <= 0.0);
        break;
    case 2:
        m_status = (data == 0.0);
        break;
    case 3:
        m_status = (data >= 0.0);
        break;
    case 4:
        m_status = (data > 0.0);
        break;
    default:
        fprintf (stdout, "ERRROR!!!! Index = %X Imm = %X\n", m_index, imm);
    }
}

 void OrbitVirtualMachine::sSqrt( int /*imm*/, int r1)
{
    m_data[m_index] = fabs(sqrt(m_data[r1]));
}

 void OrbitVirtualMachine::sCopy( int /*imm*/, int r1)
{
   m_data[m_index] = m_data[r1];
}

 void OrbitVirtualMachine::sInput( int /*imm*/, int r1)
{
    double res = m_inReg.value(r1);
    m_data[m_index] = res;
}

 std::string OrbitVirtualMachine::printD(int op, int r1, int r2)
{
    static std::stringstream ss;
    static std::string typeOp;
    ss.str("");
    switch(op)
    {
        case 1:
        typeOp = "dAdd";
        break;
        case 2:
        typeOp = "dSub";
        break;
        case 3:
        typeOp = "dMult";
        break;
        case 4:
        typeOp = "dDiv";
        break;
        case 5:
        typeOp = "dOutput";
        break;
        case 6:
        typeOp = "dPhi";
        break;
        default:
        fprintf (stdout, "ERRROR!!!! Index = %X Op = %X\n", m_index, op);
    }
    ss << typeOp << "( " << r1 << ", " << r2 <<");";
    return ss.str();
}

std::string OrbitVirtualMachine::printS(int op, int imm, int r1)
{
    static std::stringstream ss;
    static std::string typeOp;
    ss.str("");
    switch(op)
    {
        case 0:
        typeOp = "sNoop";
        break;
        case 1:
        typeOp = "sCmpz";
        break;
        case 2:
        typeOp = "sSqrt";
        break;
        case 3:
        typeOp = "sCopy";
        break;
        case 4:
        typeOp = "sInput";
        break;
        default:
        fprintf (stdout, "ERRROR!!!! Index = %X Op = %X\n", m_index, op);
    }
    ss << typeOp << "( " << imm << ", " << r1 <<");";
    return ss.str();

}



void OrbitVirtualMachine::executeD(int op, int r1, int r2)
{
    switch(op)
    {
        case 1:
        return dAdd(r1, r2);
        case 2:
        return dSub(r1, r2);
        case 3:
        return dMult(r1, r2);
        case 4:
        return dDiv(r1, r2);
        case 5:
        return dOutput(r1, r2);
        case 6:
        return dPhi(r1, r2);
        default:
        fprintf (stdout, "ERRROR!!!! Index = %X Op = %X\n", m_index, op);
    }
}

void OrbitVirtualMachine::executeS(int op, int imm, int r1)
{
    switch(op)
    {
        case 0:
        return sNoop(imm, r1);
        case 1:
        return sCmpz(imm, r1);
        case 2:
        return sSqrt(imm, r1);
        case 3:
        return sCopy(imm, r1);
        case 4:
        return sInput(imm, r1);
        default:
        fprintf (stdout, "ERRROR!!!! Index = %X Op = %X\n", m_index, op);
    }
}

void OrbitVirtualMachine::setLog(const std::string &file)
{
    m_logEnabled = true;
    m_logFile = file;
}

void OrbitVirtualMachine::generateCpp(const std::string &file)
{
    FILE * outFile;
    outFile = fopen (file.c_str() , "w" );
    fprintf (outFile, "#include \"orbitvirtualmachine.h\"\n");
    fprintf (outFile, "void OVMBinSMTH::nextCompiledStep()\n{");
    fprintf (outFile, "/// Generated for %s\n", m_logFile.c_str());
    fprintf (outFile, "   m_index = 0;");
    {
    int index = 0;
    long int code;
    int opCode;
    int imm;
    int r1;
    int r2;
    std::string command;
    while (index < m_data.size())
    {
        fprintf (outFile, "    m_index = %d;\n", index);
        code = m_code[index];
        if (code == 0)
        {
            index++;
            continue;
        }
        int opType = (code & 0xF0000000);
        if (opType == 0)
        {
            opCode = ((code & 0xFF000000) >> 24);
            imm = (code>>21) & 7;
            r1 = ((code & 0x3FFF));
            command =  printS(opCode, imm, r1);
        }
        else
        {
            opCode = ((code & 0xF0000000) >> 28);
            r1 = ((code & 0xFFFC000) >> 14);
            r2 = (code & 0x3FFF);
            command = printD(opCode, r1, r2);
        }
        fprintf (outFile, "    %s\n", command.c_str());
        index++;
    }
    }
    fprintf (outFile, "statsOutput();\n");
    fprintf (outFile, "m_count++;\n");
    fprintf (outFile, "}\n");
    fclose(outFile);
}

std::string OrbitVirtualMachine::getStatusString()
{
    static char buff[1024];
    double
            score = m_outReg[OP_SCORE],
            fuel = m_outReg[OP_FUEL],
            sx = m_outReg[OP_SPACE_X],
            sy = m_outReg[OP_SPACE_Y],
            radiusTarget = m_outReg[OP_TARGET_R];
    double radiusCurrent = sqrt(sx*sx + sy*sy);
    sprintf(buff, "Step: %4d Score: %8f Fuel: %8f sx= %8f sy= %8f sr = %8f target = %8f \n",
         m_count,
         score,
         fuel,
         sx,
         sy,
         radiusCurrent,
         radiusTarget);
    return std::string(buff);
}
